Refactor client credential material resolution#5835
Conversation
There was a problem hiding this comment.
Pull request overview
Refactors confidential client credential handling by introducing a normalized “credential material” model (context in → material out) and updating the token request pipeline to resolve/apply that material consistently, including mTLS/PoP constraints and diagnostics.
Changes:
- Introduces
CredentialContext,CredentialMaterial,CredentialMaterialResolver,CredentialSource, andClientAuthMode; updatesIClientCredentialto returnCredentialMaterial. - Updates
TokenClientto resolve the token endpoint once and pass it through credential material resolution, then apply returned body parameters and resolved certificate. - Adds
MsalError.InvalidCredentialMaterialand adds unit coverage viaCredentialMatrixTests.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Identity.Test.Unit/RequestsTests/CredentialMatrixTests.cs | Adds matrix/edge-case tests for credential material resolution across Regular vs mTLS modes. |
| src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt | Public API addition for MsalError.InvalidCredentialMaterial. |
| src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt | Public API addition for MsalError.InvalidCredentialMaterial. |
| src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt | Public API addition for MsalError.InvalidCredentialMaterial. |
| src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt | Public API addition for MsalError.InvalidCredentialMaterial. |
| src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt | Public API addition for MsalError.InvalidCredentialMaterial. |
| src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt | Public API addition for MsalError.InvalidCredentialMaterial. |
| src/client/Microsoft.Identity.Client/OAuth2/TokenClient.cs | Uses CredentialMaterialResolver and applies returned request parameters + resolved certificate. |
| src/client/Microsoft.Identity.Client/MsalError.cs | Adds InvalidCredentialMaterial error code with guidance. |
| src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj | Adds item entries for new credential files (currently introduces duplicate compile item risk). |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/IClientCredential.cs | Replaces “apply to OAuth2Client” API with GetCredentialMaterialAsync(context, ct). |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialSource.cs | New enum indicating static vs callback origin. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialMaterialResolver.cs | New centralized resolver building context + validating returned material. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialMaterial.cs | New normalized output type for credential resolution. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialContext.cs | New immutable input context type for credential resolution. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientCredentialApplicationResult.cs | Removes legacy result type. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientAuthMode.cs | New enum for Regular vs MtlsMode. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientAssertionStringDelegateCredential.cs | Updates to return CredentialMaterial and enforce mTLS incompatibility. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientAssertionDelegateCredential.cs | Updates to return CredentialMaterial (JWT bearer vs JWT-PoP + optional cert). |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs | Updates to return CredentialMaterial and reject mTLS mode. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs | Updates to return CredentialMaterial and reject mTLS mode. |
| src/client/Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs | Updates to return empty params + cert in mTLS mode, assertion params + cert in regular. |
tests/Microsoft.Identity.Test.Unit/RequestsTests/CredentialMatrixTests.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialMaterialResolver.cs
Outdated
Show resolved
Hide resolved
.../Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
Show resolved
Hide resolved
.../Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
Outdated
Show resolved
Hide resolved
tests/Microsoft.Identity.Test.Unit/RequestsTests/CredentialMatrixTests.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientAuthMode.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialContext.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialContext.cs
Outdated
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialMaterialResolver.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialMaterialResolver.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/OAuthMode.cs
Show resolved
Hide resolved
| public IReadOnlyDictionary<string, string> TokenRequestParameters { get; } | ||
|
|
||
| /// <summary>Whether the credential was resolved statically or via a runtime callback.</summary> | ||
| public CredentialSource Source { get; } |
There was a problem hiding this comment.
This is not used except for logging. Pls remove it.
src/client/Microsoft.Identity.Client/Internal/ClientCredential/SecretStringClientCredential.cs
Outdated
Show resolved
Hide resolved
...lient/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs
Outdated
Show resolved
Hide resolved
| /// Handles client assertions supplied via a delegate that returns a | ||
| /// <see cref="ClientSignedAssertion"/> (JWT + optional certificate bound for mTLS-PoP). | ||
| /// </summary> | ||
| internal sealed class ClientAssertionDelegateCredential : IClientCredential, IClientSignedAssertionProvider |
There was a problem hiding this comment.
Can you add tests or a debug assertion to ensure that each credential, and especially this one, is called once and only once per token request?
There was a problem hiding this comment.
Added test coverage to verify the callback-backed credential path is invoked exactly once per token request.
...ent/Microsoft.Identity.Client/Internal/ClientCredential/ClientAssertionDelegateCredential.cs
Outdated
Show resolved
Hide resolved
| { | ||
| { | ||
| OAuth2Parameter.ClientAssertionType, | ||
| useJwtPop ? OAuth2AssertionType.JwtPop : OAuth2AssertionType.JwtBearer |
There was a problem hiding this comment.
Pls compute the assertion type only once. You seem to do it twice, once here and once at line 85 for logging.
There was a problem hiding this comment.
Computed the assertion type once and reused it for both logging and request parameters
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialSource.cs
Outdated
Show resolved
Hide resolved
...lient/Microsoft.Identity.Client/Internal/ClientCredential/SignedAssertionClientCredential.cs
Outdated
Show resolved
Hide resolved
| /// </list> | ||
| /// <see langword="null"/> when no certificate is involved (secret, plain JWT assertion). | ||
| /// </summary> | ||
| public X509Certificate2 ResolvedCertificate { get; } |
There was a problem hiding this comment.
This should be MtlsCertificate ?
.../Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
Outdated
Show resolved
Hide resolved
tests/Microsoft.Identity.Test.Unit/RequestsTests/CredentialMatrixTests.cs
Show resolved
Hide resolved
.../Microsoft.Identity.Client/Internal/ClientCredential/CertificateAndClaimsClientCredential.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialMaterialResolver.cs
Outdated
Show resolved
Hide resolved
0cb95ce to
302a6df
Compare
302a6df to
1503f8e
Compare
|
FYI: #5835 is the follow-up to (and supersedes) #5748. I went through every open thread/comment on #5748 and carried them forward here:
|
tests/Microsoft.Identity.Test.Unit/RequestsTests/CredentialMatrixTests.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialMaterial.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/ClientSecretCredential.cs
Show resolved
Hide resolved
src/client/Microsoft.Identity.Client/Internal/ClientCredential/CredentialMaterialResolver.cs
Show resolved
Hide resolved
tests/Microsoft.Identity.Test.Unit/RequestsTests/CredentialMatrixTests.cs
Show resolved
Hide resolved
|
@bgavrilMS @neha-bhargava can you please review this |
tests/Microsoft.Identity.Test.Unit/RequestsTests/CredentialMatrixTests.cs
Show resolved
Hide resolved
a431912 to
127e7df
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
tests/Microsoft.Identity.Test.Unit/RequestsTests/CredentialMatrixTests.cs:418
CredentialMaterial_NullTokenRequestParameters_ThrowsInvalidOperationExceptionasserts anArgumentNullExceptionis thrown. The test name should be updated to match the asserted exception type to avoid confusion when diagnosing failures.
bd2f1ce to
1503f8e
Compare
a431912 to
1503f8e
Compare
| public void CredentialMaterial_NullTokenRequestParameters_ThrowsInvalidOperationException() | ||
| { | ||
| // CredentialMaterial rejects null TokenRequestParameters at construction time. | ||
| Assert.ThrowsExactly<InvalidOperationException>( | ||
| () => new CredentialMaterial(null)); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public void CredentialMaterial_EmptyTokenRequestParameters_IsValid() |
There was a problem hiding this comment.
Test name says it throws InvalidOperationException, but the assertion verifies an ArgumentNullException (and the comment also describes an ArgumentNullException scenario). Rename the test (or change the asserted exception) so the name matches the behavior being validated.
|
@bgavrilMS @neha-bhargava can you please review this |
What does this PR do?
This PR refactors client credential handling to separate credential material generation from token request application, while also addressing review feedback from the earlier iteration.
Main changes
Credential material refactor
ClientCredentialApplicationResultflow with:CredentialMaterialCredentialContextCredentialMaterialResolverCredentialSourceClientAuthModeIClientCredentialto return normalized credential material throughGetCredentialMaterialAsync(...).TokenClient update
TokenClientto resolve the token endpoint once and pass that exact endpoint through credential resolution.Credential implementations
Updated credential implementations to follow the new model:
mTLS / PoP handling
ClientSignedAssertionwithTokenBindingCertificatefor JWT-PoP scenarios.Logging
CredentialContext.Logger.Public surface / errors
MsalError.InvalidCredentialMaterialTests
CredentialMatrixTestscovering the canonical credential matrix and edge cases.Why
This change makes credential resolution more explicit and easier to reason about by:
Validation